package asteroids.model;
import asteroids.model.programs.parsing.*;

//TODO
/**
 * Uitzonderingen checken, illegal argument etc...
 * try catch statements plaatsen
 */
import java.util.List;
//import java.awt.List;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Random;
import java.util.*;
import ModelExceptions.*;
import asteroids.*;
import asteroids.ModelException;
import asteroids.model.programs.Program;
import asteroids.model.programs.parsing.ProgramParser;
import asteroids.model.programs.parsing.expressions.E;
import asteroids.model.programs.parsing.statements.S;
import asteroids.model.programs.parsing.types.T;

/**
 * @method 	public asteroids.IFacade.ParseOutcome<Program> loadProgramFromStream(InputStream stream) throws IOException 
 * 				Code from website: http://stackoverflow.com/questions/309424/read-convert-an-inputstream-to-a-string (combination of multiple answers) //Date 10/05/2013
 * @author Thomas
 *
 */
public class Facade implements IFacade <World, Ship, Asteroid, Bullet, Program> {

	public Ship createShip(double x,double y, double xVelocity, double yVelocity,double radius,double angle,double mass) throws ModelException {
		try {
			Ship ship1 = new Ship(x, y, xVelocity, yVelocity, radius, angle, mass);
			return ship1;
		}
		catch (IllegalRadiusException exc ){
			throw new ModelException(exc);
		}
		catch (OutOfBoundsException exc){
			throw new ModelException(exc);			
		}
		catch(IllegalMassException exc){
			throw new ModelException(exc);
		}
	}



	/**
	 * Return the x-coordinate of <code>ship</code>.
	 */
	public double getX(Ship ship) throws ModelException{
		try{
			return ship.getXCoordinate();
		}
		catch (OutOfBoundsException exc){
			throw new ModelException(exc);			
		}

	}

	/**
	 * Return the y-coordinate of <code>ship</code>.
	 */
	public double getY(Ship ship) throws ModelException{
		try{
			return ship.getYCoordinate();
		}
		catch (OutOfBoundsException exc){
			throw new ModelException(exc);			
		}
	}

	/**
	 * Return the velocity of <code>ship</code> along the X-axis.
	 */
	public double getXVelocity(Ship ship){
		return ship.getXVelocity();
	}

	/**
	 * Return the velocity of <code>ship</code> along the Y-axis.
	 */
	public double getYVelocity(Ship ship){
		return ship.getYVelocity();		  
	}

	/**
	 * Return the radius of <code>ship</code>.
	 */
	public double getRadius(Ship ship) throws ModelException{
		try{
			return ship.getRadius();
		}
		catch (IllegalRadiusException exc){
			throw new ModelException(exc);			
		}
	}

	/**
	 * Return the direction of <code>ship</code> (in radians).
	 */
	public double getDirection(Ship ship){
		return ship.getAngle();		  
	}

	/**
	 * Update <code>ship</code>'s position, assuming it moves <code>dt</code>
	 * seconds at its current velocity.
	 */
	public void move(Ship ship, double dt){
		try{
			ship.move(dt);
		}
		catch(Exception exc){
			throw new ModelException(exc);
		}
	}

	/**
	 * Update <code>ship</code>'s velocity based on its current velocity, its
	 * direction and the given <code>amount</code>.
	 */
	public void thrust(Ship ship, double amount){
		ship.thrust(amount);
	}

	/**
	 * Update the direction of <code>ship</code> by adding <code>angle</code> (in
	 * radians) to its current direction. <code>angle</code> may be negative.
	 */
	public void turn(Ship ship, double angle){
		ship.turn(angle);
	}

	/**
	 * Return the distance between ship1 and ship2.
	 * 
	 * The absolute value of the result of this method is the minimum distance
	 * either ship should move such that both ships are adjacent. Note that the
	 * result must be negative if the ships overlap. The distance between a ship
	 * and itself is 0.
	 */
	public double getDistanceBetween(Ship ship1, Ship ship2) throws ModelException{
		try { 	
			return ship1.getDistanceBetween(ship2);
		}
		catch(IllegalShipException exc){
			throw new ModelException(exc);
		}
		catch(IncalculateableRangeException exc){
			throw new ModelException(exc);
		}
	}

	/**
	 * Check whether <code>ship1</code> and <code>ship2</code> overlap. A ship
	 * always overlaps with itself.
	 */
	public boolean overlap(Ship ship1, Ship ship2) throws ModelException{
		try{
			return ship1.overlap(ship2);
		}
		catch (IllegalShipException exc) {
			throw new ModelException(exc);
		}
		catch(IncalculateableRangeException exc) {
			throw new ModelException(exc);
		}

	}

	/**
	 * Return the number of seconds until the first collision between
	 * <code>ship1</code> and <code>ship2</code>, or Double.POSITIVE_INFINITY if
	 * they never collide. A ship never collides with itself.
	 */
	public double getTimeToCollision(Ship ship1, Ship ship2) throws ModelException{
		try{
			return (ship1).getTimeToCollision(ship2);
		}
		//		catch (IllegalVectorProductException exc){
		//			return Double.POSITIVE_INFINITY;
		//		}
		//		catch(IncalculateableRangeException exc){
		//			return Double.POSITIVE_INFINITY;
		//		}
		//		catch(IllegalRadiusException exc ){
		//			return Double.POSITIVE_INFINITY;
		//		}
		//		catch(IllegalShipException exc){
		//			return Double.POSITIVE_INFINITY;
		//		}
		catch(Exception exc){
			throw new ModelException(exc);
		}
	}

	/**
	 * Return the first position where <code>ship1</code> and <code>ship2</code>
	 * collide, or <code>null</code> if they never collide. A ship never collides
	 * with itself.
	 * 
	 * The result of this method is either null or an array of length 2, where the
	 * element at index 0 represents the x-coordinate and the element at index 1
	 * represents the y-coordinate.
	 */
	@Override
	public World createWorld(double width, double height) {
		return new World(height, width);
	}



	@Override
	public double getWorldWidth(World world) {
		return world.getWorldWidth();
	}



	@Override
	public double getWorldHeight(World world) {
		return world.getWorldHeight();
	}



	@Override
	public Set<Ship> getShips(World world) {
		return world.getShips();
	}



	@Override
	public Set<Asteroid> getAsteroids(World world) {
		return world.getAsteroids();
	}



	@Override
	public Set<Bullet> getBullets(World world) {
		return world.getBullets();
	}



	@Override
	public void addShip(World world, Ship ship) {
		try{
			world.addAsCollisionObject(ship);
		}
		catch(Exception exc){
			throw new ModelException(exc);
		}

	}



	@Override
	public void addAsteroid(World world, Asteroid asteroid) {
		try{
			world.addAsCollisionObject(asteroid);
		}
		catch(Exception exc){
			throw new ModelException(exc);
		}
	}



	@Override
	public void removeShip(World world, Ship ship) {
		try{
			world.removeAsCollisionObject(ship);
		}
		catch(Exception exc){
			throw new ModelException(exc);
		}

	}



	@Override
	public void removeAsteroid(World world, Asteroid asteroid) {
		try{
			world.removeAsCollisionObject(asteroid);
		}
		catch(Exception exc){
			throw new ModelException(exc);
		}

	}



	@Override
	public void evolve(World world, double dt,CollisionListener collisionListener) {
		world.evolve(dt,collisionListener);
	}



	@Override
	public boolean isShip(Object o) {
		if(CollisionObject.class.isInstance(o))
			return ((CollisionObject) o).isShip();
		return false;
	}



	@Override
	public double getShipX(Ship ship) {
		return ship.getXCoordinate();
	}



	@Override
	public double getShipY(Ship ship) {
		return ship.getYCoordinate();
	}



	@Override
	public double getShipXVelocity(Ship ship) {
		return ship.getXVelocity();
	}



	@Override
	public double getShipYVelocity(Ship ship) {
		return ship.getYVelocity();
	}



	@Override
	public double getShipRadius(Ship ship) {
		return ship.getRadius();
	}



	@Override
	public double getShipDirection(Ship ship) {
		if(ship.isShip()){
			return ship.getAngle();
		}
		return 0;
	}



	@Override
	public double getShipMass(Ship ship) {
		return ship.getMass();
	}



	@Override
	public World getShipWorld(Ship ship) {
		return ship.getWorld();
	}



	@Override
	public boolean isShipThrusterActive(Ship ship) {
		return ship.isShipThrusterActive();
	}



	@Override
	public void setThrusterActive(Ship ship, boolean active) {
		ship.setThrusterActive(active);
	}



	@Override
	public void fireBullet(Ship ship) {
		try{
			ship.fireBullet();
		}
		catch(Exception exc){}
	}





	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity,double yVelocity, double radius) {
		Asteroid asteroid = new Asteroid(x,y,xVelocity,yVelocity,radius);
		return asteroid;
	}



	@Override
	public Asteroid createAsteroid(double x, double y, double xVelocity,double yVelocity, double radius, Random random) {
		return new Asteroid(x, y, xVelocity, yVelocity, radius, random);
	}



	@Override
	public boolean isAsteroid(Object o) {
		if(CollisionObject.class.isInstance(o))
			return ((CollisionObject) o).isAsteroid();
		return false;
	}



	@Override
	public double getAsteroidX(Asteroid asteroid) {
		return asteroid.getXCoordinate();
	}



	@Override
	public double getAsteroidY(Asteroid asteroid) {
		return asteroid.getYCoordinate();
	}



	@Override
	public double getAsteroidXVelocity(Asteroid asteroid) {
		return asteroid.getXVelocity();
	}



	@Override
	public double getAsteroidYVelocity(Asteroid asteroid) {
		return asteroid.getYVelocity();
	}



	@Override
	public double getAsteroidRadius(Asteroid asteroid) {
		return asteroid.getRadius();
	}



	@Override
	public double getAsteroidMass(Asteroid asteroid) {
		return asteroid.getMass();
	}



	@Override
	public World getAsteroidWorld(Asteroid asteroid) {
		return asteroid.getWorld();
	}



	@Override
	public boolean isBullets(Object o) {
		if(CollisionObject.class.isInstance(o))
			return ((CollisionObject) o).isBullet();
		return false;
	}



	@Override
	public double getBulletX(Bullet bullet) {
		return bullet.getXCoordinate();
	}



	@Override
	public double getBulletY(Bullet bullet) {
		return bullet.getYCoordinate();
	}



	@Override
	public double getBulletXVelocity(Bullet bullet) {
		return bullet.getXVelocity();
	}



	@Override
	public double getBulletYVelocity(Bullet bullet) {
		return bullet.getYVelocity();
	}



	@Override
	public double getBulletRadius(Bullet bullet) {
		return bullet.getRadius();
	}



	@Override
	public double getBulletMass(Bullet bullet) {
		return bullet.getMass();
	}



	@Override
	public World getBulletWorld(Bullet bullet) {
		return bullet.getWorld();
	}



	@Override
	public Ship getBulletSource(Bullet bullet) {
		return bullet.getShip();
	}



	//	@Override
	//	public asteroids.IFacade.ParseOutcome<Program> parseProgram(String text) {
	//		ProgramFactoryImpl factory = new ProgramFactoryImpl();
	//		ProgramParser<E, S, T> parser = new ProgramParser<>(factory);
	//		try {
	//			parser.parse(text);
	//			List<String> errors = parser.getErrors();
	//			if(! errors.isEmpty()) {
	//				return ParseOutcome.failure(errors.get(0));
	//			} else {
	//				return ParseOutcome.success(new Program(parser.getGlobals(), parser.getStatement())); 
	//			}
	//		} catch(RecognitionException e) {
	//			return ParseOutcome.failure(e.getMessage());
	//		}
	//	}


	public asteroids.IFacade.ParseOutcome<Program> parseProgram(String text) {
		ProgramFactoryImpl factory = new ProgramFactoryImpl();
		ProgramParser<E, S, T> parser = new ProgramParser<>(factory);
		//		try {
		parser.parse(text);
		List<String> errors = parser.getErrors();
		if(! errors.isEmpty()) {
			return ParseOutcome.failure(errors.get(0));
		} 
		else {
			return ParseOutcome.success(new Program(parser.getGlobals(), parser.getStatement())); 
		}
		//		} 
		//		catch(RecognitionException e) {
		//			return ParseOutcome.failure(e.getMessage());
		//		}
	}




	@Override
	public asteroids.IFacade.ParseOutcome<Program> loadProgramFromStream(InputStream stream) throws IOException {
		Scanner scanner = new Scanner(stream);
		String inputString = scanner.useDelimiter("\\A").next();
		scanner.close();
		return parseProgram(inputString);
	}



	@Override
	public asteroids.IFacade.ParseOutcome<Program> loadProgramFromUrl(URL url)
			throws IOException {
		return loadProgramFromStream(url.openStream());
	}



	@Override
	public boolean isTypeCheckingSupported() {
		// TODO Auto-generated method stub
		return false;
	}



	@Override
	public asteroids.IFacade.TypeCheckOutcome typeCheckProgram(Program program) {
		// TODO Auto-generated method stub
		return null;
	}



	@Override
	public void setShipProgram(Ship ship, Program program) {
		ship.setProgram(program);
		// TODO Auto-generated method stub

	}
}



